﻿#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEngine;

namespace AI.Graph.Editor {


    public abstract class GUICanvas<TWindow> where TWindow : EditorWindow {

#region Fields

        private float m_zoom = 1;
        private float m_minZoom = .1F;
        private float m_maxZoom = 1F;
        private float m_zoomSensitivity = 12;
        private TWindow m_window;
        private Rect m_unscaledRect;
        private Rect m_scaledRect;
        private Vector2 m_offset;
        private bool m_isPanning;
        private Vector2 m_panStartPosition;

#endregion


#region Properties

        /// <summary>
        /// The EditorWindow that used this canvas.
        /// </summary>
        public TWindow window {
            get { return m_window; }
        }


        /// <summary>
        /// The unscaled canvas Rect.
        /// </summary>
        public Rect unscaledRect {
            get { return m_unscaledRect; }
        }


        /// <summary>
        /// The scaled canvas Rect.
        /// </summary>
        public Rect scaledRect {
            get { return m_scaledRect; }
        }


        /// <summary>
        /// The offset of the drawing canvas. 
        /// </summary>
        public Vector2 offset {
            get { return -m_offset; }
            protected set { m_offset = -value; }
        }


        /// <summary>
        /// The current canvas zoom.
        /// </summary>
        public float zoom {
            get { return m_zoom; }
        }


        /// <summary>
        /// The min canvas zoom.
        /// </summary>
        public float minZoom {
            get { return m_minZoom; }
        }


        /// <summary>
        /// The max canvas zoom.
        /// </summary>
        public float maxZoom {
            get { return m_maxZoom; }
        }


        /// <summary>
        /// The zoom percentage this value is between 0 if the zoom is equal to MinZoom or 1 if the zoom is equal to MaxZoom.
        /// </summary>
        public float zoomPercentage {
            get { return Math.Clamp01 (zoom, minZoom, maxZoom); }
        }


        /// <summary>
        /// The mouse position on the canvas.
        /// </summary>
        public Vector2 mousePosition {
            get {
                Vector2 position = ScreenToCanvasPoint (Event.current.mousePosition);

                // Todo precise mousePosition.
                // mousePosition.y -= 5;
                return position;
            }
        }

        public event Action onCanvasGUI;
        public event Action onBeforeCanvasGUI;
        public event Action onAfterCanvasGUI;

#endregion


        public GUICanvas (TWindow window) {
            m_window = window;
        }


        /// <summary>
        /// Set the zoom to the given value. It's automatically clamped betweem MinZoom and MaxZoom.
        /// </summary>
        /// <param name="newZoom">The new zoom.</param>
        /// <param name="canvasZoomPivot">The zoom pivot</param>
        public void SetZoom (float newZoom, Vector2 canvasZoomPivot) {
            float oldZoom = m_zoom;
            m_zoom = Mathf.Clamp (newZoom, m_minZoom, m_maxZoom);
            m_offset += (canvasZoomPivot - m_offset) - (oldZoom / m_zoom) * (canvasZoomPivot - m_offset);
        }
        

        /// <summary>
        /// Transforms position from screen space into canvas space.
        /// </summary>
        public Vector2 ScreenToCanvasPoint (Vector2 screenPoint) {
            return GUIScaler.UnscaledToScaledPoint (screenPoint, m_unscaledRect, m_offset, m_zoom);
        }


        /// <summary>
        /// Transforms position from canvas space into screen space. 
        /// </summary>
        public Vector2 CanvasToScreenPoint (Vector2 canvasPoint) {
            return GUIScaler.ScaledToUnscaledPoint (canvasPoint, m_scaledRect, m_offset, m_zoom, window);
        }


        public void OnGUI (Rect drawRect) {
            m_unscaledRect = drawRect;

//            HandleEvents (Event.current);

            OnBeforeCanvasGUI ();
            onBeforeCanvasGUI?.Invoke ();

            m_scaledRect = GUIScaler.BeginScaleArea (m_unscaledRect, m_offset, m_zoom, window);
            OnCanvasGUI ();
            onCanvasGUI?.Invoke ();
            GUIScaler.EndScaleArea ();

            OnAfterCanvasGUI ();
            onAfterCanvasGUI?.Invoke ();

            HandleEvents (Event.current);
        }


        public virtual void OnSceneGUI () { }


        public virtual void OnDisable () { }


        /// <summary>
        /// On GUI function is called before OnCanvasGUI.
        /// Rendermode is the unscaled rect.
        /// </summary>
        protected virtual void OnBeforeCanvasGUI () { }


        /// <summary>
        /// On GUI function is called after OnCanvasGUI.
        /// Rendermode is the unscaled rect.
        /// </summary>
        protected virtual void OnAfterCanvasGUI () { }


        /// <summary>
        /// On GUI function that is rendered in the scaled rect.
        /// </summary>
        protected virtual void OnCanvasGUI () { }


        void HandleEvents (Event e) {
            // zoom
            if (e.type == EventType.ScrollWheel && m_unscaledRect.Contains (e.mousePosition)) {
                float delta = -e.delta.y / 100 * m_zoomSensitivity * (Math.Clamp01 (m_zoom, m_minZoom, m_maxZoom) + 0.1F);
                SetZoom (m_zoom + delta, ScreenToCanvasPoint (e.mousePosition));

                e.Use ();
            }

            // pan start
            if (e.type == EventType.MouseDown && !m_isPanning) {
                if (e.button == 2 || e.button == 0 && e.modifiers == EventModifiers.Alt) {
                    m_isPanning = true;
                    m_panStartPosition = ScreenToCanvasPoint (e.mousePosition);

                    e.Use ();
                }
            }

            // pan
            if (e.type == EventType.MouseDrag && m_isPanning) {
                Vector2 delta = ScreenToCanvasPoint (e.mousePosition) - m_panStartPosition;
                m_offset -= delta;

                e.Use ();
            }

            // pan end
            if (e.rawType == EventType.MouseUp && m_isPanning) {
                m_isPanning = false;
                e.Use ();
            }

            OnHandleEvents (e);
        }


        /// <summary>
        /// Can be used if more canvas events are nessacary.
        /// </summary>
        /// <param name="e">The current event.</param>
        protected virtual void OnHandleEvents (Event e) { }

    }


}
#endif